"""ODOO和WMS系统的数据转换"""

from odoo import models, api, fields
from odoo.tools import float_compare
from odoo.exceptions import UserError, ValidationError


class OdooWmsConvert(models.AbstractModel):
    """通用字段"""
    _name = 'nd.data.converter'
    _description = 'ODOO和WMS的数据转换'

    def __get_product_type(self, product):
        # if product.gsp_type == 'medicine':
        #     return '药品'
        # elif product.gsp_type == 'instrument':
        #     return '医疗器械'
        # else:
        #     return ''
        return ''

    def __get_medical_type(self, product):
        """获取MedicalType字段值"""
        if product.gsp_type == 'medicine':
            return product.dosage_id and product.dosage_id.name or ''
        elif product.gsp_type == 'instrument':
            return product.instrument_categ_id and product.instrument_categ_id.name or ''
        else:
            return ''

    def _get_sync_fields(self):
        """返回产品里需要同步的字段,当这些字段发生改动时,需要重新同步至WMS"""
        if self._name in ['product.template', 'product.product']:
            return set(['company_id', 'default_code', 'name', 'validity_date', 'expire_manage', 'tracking',
                        'expire_date', 'gsp_type', 'state', 'approve_num', 'certificate_num', 'nd_size',
                        'is_special', 'barcode', 'uom_id', 'producer_id', 'gsp_storage_condition_id'])
        elif self._name in ['res.partner']:
            return set(['nd_customer_code', 'nd_supplier_code', 'name', 'nd_short', 'nd_address',
                        'phone', 'currency_id', 'ref'])
        else:
            raise UserError(f'针对表单"{self._name}"暂时没有待同步字段')

    def odoo_product_to_wms(self, products):
        """
        将ODOO产品转化成WMS产品信息
        :param products: product.product。ODOO产品
        :return: 符合WMS系统的产品资料字典列表
        """
        # TODO 取消入库单\取消出库单
        datas = []
        for product in products:
            approve_num = ''
            if product.gsp_type == 'medicine':
                approve_num = product.approve_num
            elif product.gsp_type == 'instrument':
                approve_num = product.certificate_num.name or ''

            data = {
                'CustomerID': self.__get_partner_code(product.company_id.partner_id),    # TODO,可以不用？
                'SKU': product.default_code,
                'Descr_C': product.name,
                'ShelfLife': product.validity_date,
                'ReservedField12': product.expire_date,
                'FreightClass': self.__get_product_type(product),
                'SpecialMaintenance': 'Y',       # TODO 重点养护
                'FirstOP': product.state == 'confirm' and 'Y' or 'N',
                'MedicalType': self.__get_medical_type(product),
                'ApprovalNo': approve_num,
                'MedicineSpecicalControl': product.is_special and 'Y' or 'N',
                # 'SerialNoCatch': 'N',    # TODO 是否采集序列号
                # 'Alternate_SKU1': product.barcode,   # 条形码
                'Alternate_SKU2': product.default_code,
                'Alternate_SKU1': product.nd_size,
                # 'Alternate_SKU3': product.nd_size,    # 这个字段不传值
                'Alternate_SKU4': product.uom_id.name,
                # 'Alternate_SKU4': product.producer_id.name or '',
                'SKU_Group3': product.gsp_storage_condition_id.name or '',
                # 'SKU_Group4': product.tracking == 'lot' and 1 or 0,     # 是否启用批次
                'ReservedField01': product.tracking == 'lot' and 1 or 0,     # 是否启用批次
                'ReservedField02': product.expire_manage and 1 or 0,         # 是否效期管理
                'SKU_Group7':  product.gsp_storage_condition_id.code or '',  # 存储条件编码
                'SKU_Group6': product.gsp_categ_id.code or '',               # 商品分类编码
            }
            datas.append(data)
        return {'xmldata': {'header': datas}}
        # return datas

    def __get_partner_code(self, partner):
        """获取指定partner的编码，供应商就返回供应商编码，客户就返回客户编码。TODO ODOO和WMS的差异问题"""
        code = False
        if partner.customer:
            code = partner.nd_customer_code
        elif partner.supplier:
            code = partner.nd_supplier_code

        if not code:
            raise UserError(f'合作伙伴：{partner.display_name} 没有对应编码！')
        return code

    def __get_partner_type(self, partner):
        """
        获取partner的客商类型，货主是OW(客户)  供应商是VE(供应商)  收货人是CO(客户地址)，
        # 2021-3-10，某些情况下，例如固定资产出库，是需要分别在ODOO、WMS做出库单，
        但是 ODOO是以货主（OW）的身份同步的客户资料，这种情况下，WMS那边做出固定资产出库时
        将无法选择对应的客户（因为他们的出库单只能选收货人，但是ODOO同步过去的全是货主），
        因此这里额外添加以CO身份同步的功能。FIXME 也许更好的方式是自动同步出库单，有待研究。
        :param partner: res.partner,待推送的客户
        :return:
        """
        if partner.customer:
            if self.env.context.get('sync_partner_as_co', False):
                return 'CO'
            return 'OW'
        elif partner.supplier:
            return 'VE'
        else:
            raise UserError(f'无法获取合作伙伴：{partner.name} 的类型')

    def odoo_partners_to_wms(self, partners):
        """
        将合作伙伴资料转化成符合WMS需求的参数字典,CustomerID和和Customer_Type来唯一确定一个合作伙伴
        :param partners: res.partner, ODOO客户资料
        :return: [{}]符合WMS系统的客商资料参数字典列表
        TODO 既是供应商也是客户的，在WMS里表现为两个客户
        """
        datas = []
        for partner in partners:
            if partner.customer or partner.supplier:
                data = {
                    'CustomerID': self.__get_partner_code(partner),
                    'Customer_Type': self.__get_partner_type(partner),
                    'Descr_C': partner.name,
                    'Descr_E': partner.nd_short,
                    'Address1': partner.nd_address or '',
                    'Contact1': '',     # TODO
                    'Contact1_Tel1': partner.phone or '',
                    'Contact1_Title': '',
                    'Contact1_Email': '',
                    'Currency': partner.currency_id.name or '',
                    'easycode': partners.ref or '',
                }
                datas.append(data)
        if not datas:
            return False
        return {'xmldata': {'header': datas}}
        # return datas

    def _get_warehouse_code(self, picking_scrap_id):
        """根据调拨单获取对应的仓库编码"""
        if picking_scrap_id._name == 'stock.picking':
            return picking_scrap_id.picking_type_id.warehouse_id.code
        elif picking_scrap_id._name == 'stock.scrap':
            all_warehouses = self.env['stock.warehouse'].sudo().search([])
            for warehouse in all_warehouses:
                locations = self.env['stock.location'].search([('id', 'child_of', warehouse.view_location_id.id)])
                if picking_scrap_id.location_id in locations:
                    return warehouse.code
        else:
            raise UserError(f'不能识别的单据：{picking_scrap_id._name}')

    def odoo_in_pickings_to_wms(self, pickings, order_type='PO'):
        """
        将入库单资料转化成符合WMS需求的参数字典
        :param pickings: stock.picking，入库单
        :param order_type: char，入库单类型,默认为PO采购入库,还有RT销售退货
        :return: [{}] 符合WMS系统需求的入库单参数字典列表
        """
        # TODO 入库单ID字段是H_EDI_09和 POReference1
        datas = []
        for picking in pickings:
            partner_id, CustomerID = self._get_partner_and_customer_id(picking, order_type)
            # 货主是当前公司对应的partner的code
            data = {
                # 'PONo': picking.name,
                # 'POType': 'PO',
                'OrderType': order_type,  # TODO 订单类型（类型代码）
                'CustomerID': CustomerID,
                'ExpectedArriveTime1': picking.scheduled_date,
                # 'POReference1': str(purchase_id.id),
                # 'POReference1': picking.id,
                'OrderNo': picking.id,
                'SupplierID': self.__get_partner_code(partner_id),
                'Supplier_Name': partner_id.name,
                # 'H_EDI_09': purchase_id.id,
                'H_EDI_09': picking.id,
                'WarehouseID': self._get_warehouse_code(picking),
                'Notes': picking.purchase_id and picking.purchase_id.notes or '',
            }
            line_items = []
            # 不管产品是否批次管理，一律看是否有stock.move.line,有，则根据stock.move.line生成参数，否则根据stock.move来
            for move in picking.move_lines:
                if not move.move_line_ids:
                    OrderedQty = max([move.product_uom_qty or 0, move.quantity_done or 0])
                    details = {
                        'LineNo': '',  # TODO ??
                        'CustomerID': CustomerID,
                        'SKU': move.product_id.default_code,
                        'OrderedQty': OrderedQty,
                        'OrderedQty_Each': OrderedQty,
                        'D_EDI_08': '',  # TODO 明细行号
                        'D_EDI_09': move.id,
                    }
                    line_items.append(details)
                else:
                    for line in move.move_line_ids:
                        OrderedQty = max([line.product_uom_qty or 0, line.qty_done or 0])
                        details = {
                            'LineNo': '',  # TODO ??
                            'CustomerID': CustomerID,
                            'SKU': line.product_id.default_code,
                            'OrderedQty': OrderedQty,
                            'OrderedQty_Each': OrderedQty,
                            'D_EDI_08': '',  # TODO 明细行号
                            'D_EDI_09': line.move_id.id,
                            'LotAtt04': line.lot_id.name or '',  # todo 批号
                            'LotAtt08': 'N',  # 状态状态，固定为合格品 传合格传‘N’,不合格传‘Y
                        }
                        if line.product_id.expire_manage and line.lot_id:
                            nd_production_date = line.lot_id.nd_production_date
                            life_date = line.lot_id.life_date
                            create_date = line.lot_id.create_date
                            details.update({
                                'LotAtt01': nd_production_date and fields.Date.to_string(nd_production_date) or '',  # TODO 批次生产日期
                                'LotAtt02': life_date and fields.Date.to_string(life_date) or '',  # TODO 批次失效日期
                                'LotAtt03': create_date and fields.Date.to_string(create_date) or '',  # TODO 批次入库日期
                            })
                        line_items.append(details)

            data['detailsItem'] = line_items
            datas.append(data)
        if not datas:
            return False
        return {'xmldata': {'header': datas}}

    def __get_lot_info(self, pick_line):
        """获取出库明细的批次信息"""
        if pick_line._name == 'stock.move':
            if pick_line.move_line_ids:
                return pick_line.move_line_ids[0].lot_id.name
        elif pick_line._name == 'stock.move.line':
            return pick_line[0].lot_id.name
        else:
            raise UserError(f'不正确的移动行类型：{pick_line._name}')

    def _get_partner_and_customer_id(self, picking_scrap, order_type):
        """返回调拨单相关的货主ID 和 交易伙伴"""
        if order_type in ['PT', 'PO']:
            purchase_id = picking_scrap.purchase_id
            partner_id = purchase_id.partner_id
            CustomerID = self.__get_partner_code(purchase_id.company_id.partner_id)
        elif order_type in ['SO', 'RT', 'JS', 'JH']:
            sale_id = picking_scrap.sale_id
            partner_id = sale_id.partner_id
            CustomerID = self.__get_partner_code(sale_id.company_id.partner_id)
        elif order_type in ['XH']:
            # 此时
            partner_id = False
            CustomerID = self.__get_partner_code(picking_scrap.company_id.partner_id)
        else:
            raise UserError(f'不能识别的调拨单类型"{order_type}"')

        return partner_id, CustomerID

    def odoo_out_pickings_to_wms(self, pickings, order_type='SO'):
        """
        将出库单资料转化成符合WMS需求的参数字典(可能是出库单\报废单\不合格退货单)
        :param pickings: stock.picking，出库单
        :param order_type: char，出库单类型,默认SO销售出库,还有PT采购退货,XH销毁出库等
        :return: [{}] 符合WMS系统需求的出库单参数字典列表
        """
        # TODO 出库单ID是OrderNo, 批次的日期信息, 报废单,不合格品退货单
        datas = []
        for picking in pickings:
            # TODO 没有sale的情况？？
            partner_id, CustomerID = self._get_partner_and_customer_id(picking, order_type)
            data = {
                # 'OrderNo': sale_id.id,
                'OrderNo': picking.id,
                'OrderType': order_type,   # TODO
                'CustomerID': CustomerID,
                'RequiredDeliveryTime': picking.scheduled_date,
                # 'ExpectedArriveTime1': picking.scheduled_date,
                # 'SOReference1': sale_id.id,
                'ConsigneeID': self.__get_partner_code(partner_id),
                'ConsigneeName': partner_id.name,
                # 'Notes': picking.note or '',
                'WarehouseID': self._get_warehouse_code(picking),
                'Priority': picking.priority,     # todo
                'Notes': picking.sale_id and picking.sale_id.note or '',
            }
            line_items = []
            # 不管产品是否批次管理，一律看是否有stock.move.line,有，则根据stock.move.line生成参数，否则根据stock.move来，如果
            # 有批次管理，但是没有stock.move.line就抛出错误
            for move in picking.move_lines:
                if move.product_id.tracking != 'lot':
                    QtyOrdered = max([move.product_uom_qty or 0, move.quantity_done or 0])
                    details = {
                        'LineNo': '',  # TODO ??
                        'D_EDI_05': move.id,  # TODO 需确认
                        'CustomerID': CustomerID,
                        'SKU': move.product_id.default_code,
                        # 'LotAtt04': self.__get_lot_info(line),  # todo 这种情况下无批号
                        'QtyOrdered': QtyOrdered,
                        'QtyOrdered_Each': QtyOrdered,
                        'D_EDI_08': move.product_uom.name,
                        'UserDefine1': picking.location_id.name,
                    }
                    line_items.append(details)
                else:
                    if not move.move_line_ids:
                        raise UserError(f'产品{move.product_id.name}有批次管理，但是出库单上没有填写批次信息')
                    for line in move.move_line_ids:
                        # 并不是总是有预留信息、明细行等
                        QtyOrdered = max([line.product_uom_qty or 0, line.qty_done or 0])
                        details = {
                            'LineNo': '',
                            'D_EDI_05': line.move_id.id,  # TODO 需确认
                            'CustomerID': CustomerID,
                            'SKU': line.product_id.default_code,
                            # 一个SKU只能选一个批次号，可以不传
                            # 'LotAtt04': self.__get_lot_info(line),  # todo 批号
                            # 这里看取”已保留product_uom_qty“还是”完成qty_done“，后者在自动保留后是0，所以取前者
                            'QtyOrdered': QtyOrdered,
                            'QtyOrdered_Each': QtyOrdered,
                            'D_EDI_08': line.product_uom_id.name,
                            'UserDefine1': picking.location_id.name,
                            'LotAtt04': line.lot_id.name or '',  # todo 批号
                            'LotAtt08': 'N',  # 状态状态，固定为合格品 合格传‘N’,不合格传‘Y
                        }
                        if line.product_id.expire_manage and line.lot_id:
                            nd_production_date = line.lot_id.nd_production_date
                            life_date = line.lot_id.life_date
                            create_date = line.lot_id.create_date
                            details.update({
                                'LotAtt01': nd_production_date and fields.Date.to_string(nd_production_date) or '',  # TODO 批次生产日期
                                'LotAtt02': life_date and fields.Date.to_string(life_date) or '',  # TODO 批次失效日期
                                'LotAtt03': create_date and fields.Date.to_string(create_date) ,  # TODO 批次入库日期
                            })
                        line_items.append(details)

            data['detailsItem'] = line_items
            datas.append(data)
        if not datas:
            return False
        return {'xmldata': {'header': datas}}

    def odoo_scraps_to_wms(self, scraps):
        """
        将报废单资料转化成符合WMS需求的参数字典
        :param scraps: stock.scrap，报废单
        :return: [{}] 符合WMS系统需求的出库单参数字典列表
        """
        # TODO 出库单ID是OrderNo, 批次的日期信息, 报废单,不合格品退货单
        datas = []
        order_type = 'XH'
        for scrap in scraps:
            # TODO 没有sale的情况？？
            partner_id, CustomerID = self._get_partner_and_customer_id(scrap, order_type)
            data = {
                # 'OrderNo': sale_id.id,
                'OrderNo': scrap.id,
                'OrderType': order_type,  # TODO
                'CustomerID': CustomerID,
                'RequiredDeliveryTime': scrap.date_expected,
                'ConsigneeID': scrap.scrap_code,
                'ConsigneeName': scrap.receive_employee_id.name,
                'Notes': '',
                'WarehouseID': self._get_warehouse_code(scrap),
                # 'Priority': 1,  # todo
            }
            line_items = []

            details = {
                'LineNo': '',
                'D_EDI_05': '',  # TODO 需确认
                'CustomerID': CustomerID,
                'SKU': scrap.product_id.default_code,
                # 一个SKU只能选一个批次号，可以不传
                # 'LotAtt04': self.__get_lot_info(line),  # todo 批号
                # 这里看取”已保留product_uom_qty“还是”完成qty_done“，后者在自动保留后是0，所以取前者
                'QtyOrdered': scrap.scrap_qty,
                'QtyOrdered_Each': scrap.scrap_qty,
                'D_EDI_08': scrap.product_uom_id.name,
                'UserDefine1': scrap.location_id.name,
                'LotAtt04': scrap.lot_id.name or '',  # todo 批号
                'LotAtt08': 'N',  # 状态状态，固定为合格品 传合格传‘N’,不合格传‘Y
            }
            if scrap.product_id.expire_manage and scrap.lot_id:
                nd_production_date = scrap.lot_id.nd_production_date
                life_date = scrap.lot_id.life_date
                create_date = scrap.lot_id.create_date
                details.update({
                    'LotAtt01': nd_production_date and fields.Date.to_string(nd_production_date) or '',  # TODO 批次生产日期
                    'LotAtt02': life_date and fields.Date.to_string(life_date) or '',  # TODO 批次失效日期
                    'LotAtt03': create_date and fields.Date.to_string(create_date) ,  # TODO 批次入库日期
                })
            line_items.append(details)
            data['detailsItem'] = line_items
            datas.append(data)
        if not datas:
            return False
        return {'xmldata': {'header': datas}}

    def _get_wms_picking_type(self, pick):
        """获取调拨单对于的WMS单据类型
        可能的出库：SO销售\PT采购退货\JS寄售出库\XH销毁出库
        可能的入库：PO采购\RT销售退货\JT寄售退货\JH寄售退还
        """
        # TOdo 寄售的？
        is_return = bool(pick.move_lines.mapped('origin_returned_move_id'))
        if pick.picking_type_id == self.env.ref('nd_consignment.nd_consignment_return_picking_type'):
            return 'JH'
        elif pick.picking_type_id == self.env.ref('nd_consignment.nd_consignment_picking_type'):
            return 'JS'

        if is_return:
            if pick.picking_type_code == 'outgoing':
                return 'PT'
            elif pick.picking_type_code == 'incoming':
                return 'RT'
        else:
            if pick.picking_type_code == 'outgoing':
                return 'SO'
            elif pick.picking_type_code == 'incoming':
                return 'PO'

    def cancel_wms_in_picks(self, picking):
        """
        将入库单资料转化成符合**取消**WMS里入库单所需求的参数字典
        :param picking: stock.picking，入库单
        :return: {'xmldata': {'data': {'ordernos': {...}}}} 符合WMS系统需求的入库单参数字典
        """
        picking.ensure_one()
        CustomerID = self.__get_partner_code(picking.company_id.partner_id)
        data = {
            'OrderNo': picking.id,  # TODO 要保持两者的单据号一致，前面的入库单也要显式传入入库单号
            # 'OrderNo': 'ASN200617000006',
            'OrderType': self._get_wms_picking_type(picking),
            'CustomerID': CustomerID,
            'WarehouseID': self._get_warehouse_code(picking),
            'Reason': '',  # todo
        }
        return {'xmldata': {'data': {'ordernos': data}}}

    def cancel_wms_out_picks(self, picking):
        """
        将出库单资料转化成符合**取消**WMS里出库单所需求的参数字典
        :param picking: stock.picking，出库单
        :return: {'xmldata': {'data': {'ordernos': {...}}}} 符合WMS系统需求的出库单参数字典
        """
        picking.ensure_one()
        CustomerID = self.__get_partner_code(picking.company_id.partner_id)
        data = {
            'OrderNo': picking.id,    # TODO 要保持两者的单据号一致，前面的入库单也要显式传入入库单号
            'OrderType': self._get_wms_picking_type(picking),
            'CustomerID': CustomerID,
            'WarehouseID': self._get_warehouse_code(picking),
            'Reason': '',   # todo
        }
        return {'xmldata': {'data': {'ordernos': data}}}
